package com.ouyunc.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ouyunc.common.constant.Auth2Constant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.function.Consumer;

/** 注意： 不要使用注解@order() 来指定顺序，会不起作用,并且如果直接请求网关的地址，也不会经过过滤器原因 请看HandlerFilterFunction, WebFilter > GlobalFilter的区别 (https://www.jianshu.com/p/19d95b44db9f)
 * 且全局WebFilter 所有请求都会走该过滤器 ，得到的请求路径path为原始的请求path如api/v1/user/test； 而 GlobalFilter 只有下游的请求才会走该全局过滤器，得到的path为 真实的下游/test
 * 通过看源码org.springframework.web.reactive.handler 分析得处如果是请求网关内部路径则指直接转发到相应接口，如果是其他下游服务路由则会走globalFilter 的过滤链；
 * 执行顺序Webfilter->securiterFiter->globalFilter
 * 目前网关作为登录客户端，登录不想被拦截，WebFilter可以拦截所有endpoint（包括直接请求网关地址接口请求以及通过网关的下游地址）
 * spring cloud gateway 某些路由中跳过全局过滤器(https://www.jianshu.com/p/42455cdc6d25)
 * @author fangzhenxun
 * @date 2019/11/12 17:09
 * @description 认证全程过滤器（只对转发服务有效），如果对于需要token 的请求没有携带token则直接过滤拦截，不往资源服务其下发
 * 实现全局过滤器，排序接口（进行指定过滤器在过滤链中的执行顺序）
 * 所有的资源请求在路由之前进行前置过滤，如果请求头不包含 Authorization参数值，直接拦截不再路由
 * @Order 值越小优先级越高
 */
@Component
public class Oauth2GlobalFilter implements GlobalFilter, Ordered{
    protected final Logger log = LoggerFactory.getLogger(Oauth2GlobalFilter.class);

    /**
     * bearer + 空格的长度
     **/
    private static final Integer BEARER_LENGTH = 7;


    /**
     * @Author fangzhenxun
     * @Description  只有请求下游服务的才会进入该过滤器（请求网关中的接口则不会走该过滤器），可以做ip黑名单，等其他拦截过滤
     * 在请求经过oauth2的过滤器后会走这里，这里进行当前用户的获取与封装
     * @Date 2020/4/10 10:58
     * @param exchange
     * @param chain
     * @return reactor.core.publisher.Mono<java.lang.Void>
     **/
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 判断是否是白名单，如果不是白名单还没有token，则直接抛异常，subscriberContext 废弃使用
        return chain.filter(exchange).contextWrite(ctx->{
            //@todo  注意这里的取值方式，有泛型，不然会出错,白名单或者不携带token下游就拿不到当前登录人
            Mono<SecurityContext> securityContextMono = ctx.<Mono<SecurityContext>>get(SecurityContext.class);
            securityContextMono.subscribe(securityContext->{
                Consumer<HttpHeaders> httpHeaders = httpHeader -> {
                    try {
                        httpHeader.set(Auth2Constant.HEADER_PRINCIPLE, URLEncoder.encode((String)securityContext.getAuthentication().getPrincipal(), Auth2Constant.UTF_8));
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                        log.error("网关请求头编码错误！");
                    }
                };
                // 构建新的请求并设置请求头
                ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders).build();
                exchange.mutate().request(serverHttpRequest).build();
            });
            return ctx;
        });
    }



    /**
     * @Author fangzhenxun
     * @Description 值越小优先级越高， 请注意各个过滤器的执行顺序
     * @Date  2019/11/13 10:22
     * @Param []
     * @return int
     **/
    @Override
    public int getOrder() {
        return 100;
    }
}
